#include <assert.h>
#include <chrono>
#include <ctime>
#include <string>
#include <string.h>  
#include <thread> 

#include <arpa/inet.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>

#include "openssl/hmac.h"
#include "webrtc/examples/websocket-client/easywsclient.hpp"
//#include "easywsclient.cpp" // <-- include only if you don't want compile separately

#include "webrtc/base/base64.h"
#include "webrtc/base/json.h"
#include "webrtc/base/logging.h"
#include "webrtc/media/engine/webrtcvideocapturerfactory.h"
#include "webrtc/modules/video_capture/video_capture_factory.h"
#include "webrtc/api/test/fakeconstraints.h"
#include "webrtc/base/physicalsocketserver.h"
#include "webrtc/base/ssladapter.h"

#include "webrtc/examples/websocket-client/websocket_client.h"

#define DTLS_ON  true
#define DTLS_OFF false

using easywsclient::WebSocket;

namespace cricket {
    class VideoRenderer;
}  // namespace cricket

const char kStreamLabel[] = "stream_label";
const char kAudioLabel[] = "audio_label";
const char kVideoLabel[] = "video_label";
const char kCandidateSdpMidName[] = "sdpMid";
const char kCandidateSdpMlineIndexName[] = "sdpMLineIndex";
const char kCandidateSdpName[] = "candidate";
const char kSessionDescriptionTypeName[] = "type";
const char kSessionDescriptionSdpName[] = "sdp";

static WebSocket::pointer ws = NULL;
bool loginInSuccess(false);
bool disconnect(false);
std::string host_address;
std::string userName("test1");
std::string peerName;
rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> peer_connection_factory_;

class Conductor {
public:
    rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection_;
    void OnSuccessCallBack(webrtc::SessionDescriptionInterface* desc){
        std::string sdp;
        desc->ToString(&sdp);
        /*
        std::string find_str("a=setup:active");
        std::string replace_str("a=setup:passive");
        for(std::string::size_type i = 0; (i = sdp.find(find_str, i)) != std::string::npos;) {
            sdp.replace(i, find_str.length(), replace_str);
            i += replace_str.length();
        }
        webrtc::SdpParseError error;
        webrtc::SessionDescriptionInterface* session_description(
            webrtc::CreateSessionDescription(desc->type(), sdp, &error));
        */
        peer_connection_->SetLocalDescription(&set_session_description_observer, desc);
        Json::Value jmessage;
        Json::Value message_sdp(sdp);
        Json::Value message_type(desc->type());
        jmessage[kSessionDescriptionTypeName] = message_type;
        jmessage[kSessionDescriptionSdpName] = message_sdp;
        SendMessage(desc->type(),jmessage);
        LOG(INFO) << __FUNCTION__ << " over" << std::endl;
    }
    
    class SetSessionDescriptionObserverImp : public webrtc::SetSessionDescriptionObserver {
    public:
        virtual void OnSuccess() {
            LOG(INFO) << __FUNCTION__ << std::endl;
        }
        virtual void OnFailure(const std::string& error) {
            LOG(INFO) << __FUNCTION__ << " " << error << std::endl;
        }
        int AddRef() const override {return 0;}
        int Release() const override {return 0;}
    };


    class CreateSessionDescriptionObserverImp : public webrtc::CreateSessionDescriptionObserver {
    private:
        Conductor & parent;
    public:
        //CreateSessionDescriptionObserverImp() {}
        CreateSessionDescriptionObserverImp(Conductor& parent) : parent(parent) {}
        // CreateSessionDescriptionObserver implementation.
        // Called on success of Create{Offer,Answer}().
        virtual void OnSuccess(webrtc::SessionDescriptionInterface* desc){
            LOG(INFO) << __FUNCTION__ << std::endl;
            parent.OnSuccessCallBack(desc);
        }
        virtual void OnFailure(const std::string& error) {
            LOG(LERROR) << error << std::endl;
        }
        int AddRef() const override {return 0;}
        int Release() const override {return 0;}
    };
    
    class PeerConnectionObserverImp : public webrtc::PeerConnectionObserver {
    public:
        virtual ~PeerConnectionObserverImp () {}
        //PeerConnectionObserver implementation. 
        void OnSignalingChange(
            webrtc::PeerConnectionInterface::SignalingState new_state) override{}
        // Called when a remote stream is added
        void OnAddStream(
            rtc::scoped_refptr<webrtc::MediaStreamInterface> stream) override {
            LOG(INFO) << __FUNCTION__ << " " << stream->label() << std::endl;
        }
        virtual void OnAddStream(webrtc::MediaStreamInterface* stream) override {}
        void OnRemoveStream(
            rtc::scoped_refptr<webrtc::MediaStreamInterface> stream) override {
            LOG(INFO) << __FUNCTION__ << " " << stream->label() << std::endl;
        }
        virtual void OnRemoveStream(webrtc::MediaStreamInterface* stream) override {}
        void OnDataChannel(
            rtc::scoped_refptr<webrtc::DataChannelInterface> channel) override {}
        void OnDataChannel(webrtc::DataChannelInterface* data_channel) override {}
        void OnRenegotiationNeeded() override {}
        void OnIceConnectionChange(
            webrtc::PeerConnectionInterface::IceConnectionState new_state) override{}
        void OnIceGatheringChange(
            webrtc::PeerConnectionInterface::IceGatheringState new_state) override{}
        void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) override {
            LOG(INFO) << __FUNCTION__ << " " << candidate->sdp_mline_index() << std::endl;
            Json::Value jmessage;
            Json::Value message_mid(candidate->sdp_mid());
            Json::Value message_mline_index(candidate->sdp_mline_index());
            jmessage[kCandidateSdpMidName] = message_mid;
            jmessage[kCandidateSdpMlineIndexName] = message_mline_index;
            std::string sdp;
            if (!candidate->ToString(&sdp)) {
                LOG(LS_ERROR) << "Failed to serialize candidate" << std::endl;
                return;
            }
            jmessage[kCandidateSdpName] = sdp;
            Conductor::SendMessage("candidate", jmessage);
        }
        void OnIceConnectionReceivingChange(bool receiving) override {}
    };
    
    
protected:
    std::map<std::string, rtc::scoped_refptr<webrtc::MediaStreamInterface> > active_streams_;
    
public:
    std::string peer_name;
    PeerConnectionObserverImp peer_connection_observer;
    SetSessionDescriptionObserverImp set_session_description_observer;
    CreateSessionDescriptionObserverImp create_session_description_observer;

    Conductor() : create_session_description_observer(*this) {}

    void Close() {
        //client_->SignOut();
        DeletePeerConnection();
    }

    bool InitializePeerConnection() {
        ASSERT(peer_connection_.get() == NULL);
        if (!CreatePeerConnection(DTLS_ON)) {
            LOG(LS_ERROR) << "Failed to initialize PeerConnectionFactory" << std::endl;
            DeletePeerConnection();
        }
        AddStreams();
        return peer_connection_.get() != NULL;
    }
    
    static void SendMessage(const std::string& type, Json::Value& payload) {
        ASSERT(ws);
        Json::Value payload_type(type);
        Json::Value payload_name(peerName);
        Json::Value message;
        message["type"] = payload_type;
        message["name"] = payload_name;
        message["payload"] = payload;
        std::string message_str = rtc::JsonValueToString(message);
        ws->send(message_str.c_str());
    }

    
protected:
    std::string GetServerTimestamp() {
        int sock = socket(AF_INET, SOCK_STREAM, 0);
        struct sockaddr_in serv_addr;
        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_addr.s_addr = inet_addr("120.25.60.70");  //具体的IP地址
        serv_addr.sin_port = htons(3033);  //端口
        connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

        const int buffer_size = 256;
        char buffer_send[buffer_size];
        char buffer_receive[buffer_size];
        for(int i = 0; i < buffer_size; i ++) {
            buffer_send[i] = '\0';
            buffer_receive[i] = '\0';
        }
        sprintf(buffer_send, "GET /timestamp HTTP/1.0\r\n\r\n");
        write(sock, buffer_send, sizeof(buffer_send)-1);
        read(sock, buffer_receive, sizeof(buffer_receive)-1);
        std::string buffer_receive_str(buffer_receive);
        std::string mark_string = "timestamp_is_";
        std::size_t mark_location = buffer_receive_str.find(mark_string);
        char timestamp[11];
        for(int i = 0; i < 10; i++){
            timestamp[i] = buffer_receive[mark_location + mark_string.size() + i];
        }
        timestamp[10] = '\0';
        std::string timestamp_str(timestamp);
        close(sock);
        return timestamp_str;
    }
    
    bool CreatePeerConnection(bool dtls) {
        char turn_key[] = "t=u408021891333";
        /*
        int time_to_live = 600;  //10 minutes;
        std::chrono::seconds ms = \
            std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch());
        LOG(LS_ERROR) << long(ms.count()) <<std::endl;
        long timestamp = long(ms.count());
        char timestamp_char_array[128];
        sprintf(timestamp_char_array, "%ld", timestamp + time_to_live);
        std::string coturnUserName(timestamp_char_array); */
        std::string coturnUserName = GetServerTimestamp();
        coturnUserName = coturnUserName + std::string(":test1");
        LOG(LS_ERROR) << coturnUserName <<std::endl;
        unsigned char digest[EVP_MAX_MD_SIZE] = {'\0'};
        unsigned int digest_len = 0;
        HMAC(EVP_sha1(), turn_key, strlen(turn_key), (unsigned char*)coturnUserName.c_str(),
             coturnUserName.size(), digest, &digest_len);
        std::string password;
        rtc::Base64::EncodeFromArray(digest, digest_len, &password);
        std::string server_url = "turn:" + host_address;
        std::string server_urls[] = {
            server_url + ":3478?transport=udp",
            server_url + ":3478?transport=tcp",
            server_url + ":3479?transport=udp",
            server_url + ":3479?transport=tcp",
        };
        webrtc::PeerConnectionInterface::RTCConfiguration config;
        for(int i = 0; i < 4; i++){
            webrtc::PeerConnectionInterface::IceServer server;
            server.uri = server_urls[i];
            server.username = coturnUserName;
            server.password = password;
            config.servers.push_back(server);
        }
        
        webrtc::FakeConstraints constraints;
        if (dtls) {
            constraints.AddOptional(webrtc::MediaConstraintsInterface::kEnableDtlsSrtp,
                                    "true");
        } else {
            constraints.AddOptional(webrtc::MediaConstraintsInterface::kEnableDtlsSrtp,
                                    "false");
        }

        constraints.AddMandatory(webrtc::MediaConstraintsInterface::kOfferToReceiveVideo, "false");
        constraints.AddMandatory(webrtc::MediaConstraintsInterface::kOfferToReceiveAudio, "false");
        peer_connection_ = peer_connection_factory_->CreatePeerConnection(
            config, &constraints, NULL, NULL, &peer_connection_observer);
        return peer_connection_.get() != NULL;
    }

    void DeletePeerConnection() {
        peer_connection_ = NULL;
        active_streams_.clear();
        //peer_connection_factory_ = NULL;
    }

    void AddStreams() {
        if (active_streams_.find(kStreamLabel) != active_streams_.end())
            return;  // Already added.
        /*
        rtc::scoped_refptr<webrtc::AudioTrackInterface> audio_track(
            peer_connection_factory_->CreateAudioTrack(
                kAudioLabel, peer_connection_factory_->CreateAudioSource(NULL)));
        */
        webrtc::FakeConstraints constraints;
        constraints.AddMandatory(webrtc::MediaConstraintsInterface::kMaxWidth, 1280);
        constraints.AddMandatory(webrtc::MediaConstraintsInterface::kMaxHeight, 720);
        constraints.AddMandatory(webrtc::MediaConstraintsInterface::kMaxFrameRate, 15);
        rtc::scoped_refptr<webrtc::VideoTrackInterface> video_track(
            peer_connection_factory_->CreateVideoTrack(
                kVideoLabel,
                peer_connection_factory_->CreateVideoSource(OpenVideoCaptureDevice(),
                                                            &constraints)));
        
        rtc::scoped_refptr<webrtc::MediaStreamInterface> stream =
            peer_connection_factory_->CreateLocalMediaStream(kStreamLabel);
        //stream->AddTrack(audio_track);
        stream->AddTrack(video_track);
        if (!peer_connection_->AddStream(stream)) {
            LOG(LS_ERROR) << "Adding stream to PeerConnection failed" << std::endl;
        }
        typedef std::pair<std::string,
                          rtc::scoped_refptr<webrtc::MediaStreamInterface> >
            MediaStreamPair;
        active_streams_.insert(MediaStreamPair(stream->label(), stream));
    }
        
    cricket::VideoCapturer* OpenVideoCaptureDevice(){
        std::vector<std::string> device_names;
        {
            std::unique_ptr<webrtc::VideoCaptureModule::DeviceInfo> info(
                webrtc::VideoCaptureFactory::CreateDeviceInfo(0));
            if (!info) {
                return nullptr;
            }
            int num_devices = info->NumberOfDevices();
            for (int i = 0; i < num_devices; ++i) {
                const uint32_t kSize = 256;
                char name[kSize] = {0};
                char id[kSize] = {0};
                if (info->GetDeviceName(i, name, kSize, id, kSize) != -1) {
                    device_names.push_back(name);
                }
            }
        }

        cricket::WebRtcVideoDeviceCapturerFactory factory;
        cricket::VideoCapturer* capturer = nullptr;
        for (const auto& name : device_names) {
            capturer = factory.Create(cricket::Device(name, 0));
            if (capturer) {
                break;
            }
        }
        return capturer;
    }
};


rtc::Thread* thread;
rtc::PhysicalSocketServer socket_server;
Conductor conductor;

void handleLogin(Json::Value jmessage) {
    bool log_result = false;
    rtc::GetBoolFromJson(jmessage["success"], &log_result);
    if(log_result){
        loginInSuccess = true;
    }
}

void handleOffer(Json::Value jmessage) {
    std::string sdp_type;
    std::string sdp;
    rtc::GetStringFromJson(jmessage["type"], &sdp_type);
    rtc::GetStringFromJson(jmessage["sdp"], &sdp);
    webrtc::SdpParseError error;
    webrtc::SessionDescriptionInterface* session_description(
        webrtc::CreateSessionDescription(sdp_type, sdp, &error));
    if (!session_description) {
        LOG(WARNING) << "Can't parse received session description message. "
                     << "SdpParseError was: " << error.description << std::endl;
        return;
    }
    conductor.peer_connection_->SetRemoteDescription(
        &conductor.set_session_description_observer, session_description);
    conductor.peer_connection_->CreateAnswer(&conductor.create_session_description_observer, NULL);
    LOG(INFO) << "handleOffer OVER" << std::endl;
    return;
}

void handleOfferLoopback(std::string& peer_name) {
    conductor.peer_name = peer_name;
    webrtc::FakeConstraints constraints;
    constraints.AddMandatory(webrtc::MediaConstraintsInterface::kOfferToReceiveVideo, "false");
    constraints.AddMandatory(webrtc::MediaConstraintsInterface::kOfferToReceiveAudio, "false");
    conductor.peer_connection_->CreateOffer(&conductor.create_session_description_observer, &constraints);
}

void handleAnswer(Json::Value jmessage) {
    std::string sdp_type;
    std::string sdp;
    rtc::GetStringFromJson(jmessage["type"], &sdp_type);
    rtc::GetStringFromJson(jmessage["sdp"], &sdp);
    webrtc::SdpParseError error;
    webrtc::SessionDescriptionInterface* session_description(
        webrtc::CreateSessionDescription(sdp_type, sdp, &error));
    if (!session_description) {
        LOG(WARNING) << "Can't parse received session description message. "
                     << "SdpParseError was: " << error.description << std::endl;
        return;
    }
    conductor.peer_connection_->SetRemoteDescription(
        &conductor.set_session_description_observer, session_description);
    
    LOG(INFO) << "handleAnswer OVER" << std::endl;
    return;
}

void handleCandidate(Json::Value jmessage) {
    std::string sdp_mid;
    int sdp_mlineindex = 0;
    std::string sdp;
    if (!rtc::GetStringFromJsonObject(jmessage, kCandidateSdpMidName, &sdp_mid) ||
        !rtc::GetIntFromJsonObject(jmessage, kCandidateSdpMlineIndexName, &sdp_mlineindex) ||
        !rtc::GetStringFromJsonObject(jmessage, kCandidateSdpName, &sdp)) {
        LOG(WARNING) << "Can't parse received message." << std::endl;
        return;
    }
    webrtc::SdpParseError error;
    std::unique_ptr<webrtc::IceCandidateInterface> candidate(
        webrtc::CreateIceCandidate(sdp_mid, sdp_mlineindex, sdp, &error));
    if (!candidate.get()) {
        LOG(WARNING) << "Can't parse received candidate message. "
                     << "SdpParseError was: " << error.description << std::endl;
        return;
    }
    if (!conductor.peer_connection_->AddIceCandidate(candidate.get())) {
        LOG(WARNING) << "Failed to apply the received candidate" << std::endl;
        return;
    }
    LOG(INFO) << "handleCandidate OVER" << std::endl;
    return;
}

void handleLeave() {
    disconnect = true;
    conductor.Close();
}

void handle_message(const std::string& message) {
    LOG(INFO) << "got message: " << message << std::endl;
    Json::Reader reader;
    Json::Value jmessage;
    if (!reader.parse(message, jmessage)) {
        LOG(WARNING) << "Received unknown message. " << message << std::endl;
        return;
    }
    std::string message_type;
    rtc::GetStringFromJson(jmessage["type"], &message_type);
    std::string message_from;
    rtc::GetStringFromJson(jmessage["from"], &message_from);
    if (message_from != "" && (message_from != peerName)) {
        peerName = message_from;
    }
    if(message_type == "login") {
        handleLogin(jmessage);
    } else if(message_type == "offer-loopback") {
        handleOfferLoopback(message_from);
    } else if(message_type == "offer") {
        handleOffer(jmessage["payload"]);
    } else if(message_type == "answer") {
        handleAnswer(jmessage["payload"]);
    } else if(message_type == "candidate") {
        handleCandidate(jmessage["payload"]);
    } else if(message_type == "leave") {
        handleLeave();
    } else {
        LOG(WARNING) << "Error message type: " << message_type << std::endl;
    }
}


void thread_entry() {
    LOG(INFO) << std::this_thread::get_id() << ":" << "RTC thread" << std::endl;
    peer_connection_factory_ = webrtc::CreatePeerConnectionFactory();
    if (peer_connection_factory_.get() == nullptr) {
        LOG(INFO) << "Error on CreatePeerConnectionFactory." << std::endl;
        return;
    }

    thread = rtc::Thread::Current();
    thread->set_socketserver(&socket_server);
    thread->Run();
    thread->set_socketserver(nullptr);
}

int webSocketClient(std::string host, std::string port) {
    host_address = host;
    rtc::LogMessage::SetLogToStderr(true);
    LOG(INFO) << std::this_thread::get_id() << ":" << "MAIN thread" << std::endl;
    ws = WebSocket::from_url("ws://" + host + ":" + port);
    assert(ws);
    
    rtc::InitializeSSL();
    
    std::thread th(thread_entry);
    if(peer_connection_factory_.get() == NULL) {
        std::this_thread::sleep_for(std::chrono::microseconds(100000));
    }
    conductor.InitializePeerConnection();

    while (ws->getReadyState() != WebSocket::CLOSED) {
        ws->poll();
        ws->dispatch(handle_message);
        // reinitial peer_connection
        if(disconnect){
            disconnect = false;
            if(peer_connection_factory_.get() == NULL) {
                std::this_thread::sleep_for(std::chrono::microseconds(100000));
            }
            conductor.InitializePeerConnection();            
        }
        if(!loginInSuccess) {
            Json::Value payload_type("login");
            Json::Value payload_name(userName);
            Json::Value payload;
            payload["type"] = payload_type;
            payload["name"] = payload_name;
            std::string payload_str = rtc::JsonValueToString(payload);
            ws->send(payload_str.c_str());
            std::this_thread::sleep_for(std::chrono::microseconds(1000000));
        }
        std::this_thread::sleep_for(std::chrono::microseconds(1000000));
    }
    delete ws;
    th.join();
    rtc::CleanupSSL();
    return 0;
}
